﻿@implements IDisposable

@code{

    string server;
    string username;
    string password;
    string viewmode;

    Renci.SshNet.SshClient client;
    Renci.SshNet.ShellStream shellstream;

    bool _isdisposed = false;

    void IDisposable.Dispose()
    {
        _isdisposed = true;
        if (client != null)
            client.Dispose();
        client = null;
    }

    string client_guid = Guid.NewGuid().ToString();

    void DoLogin()
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            BlazorSession.Current.Toast("require username");
            return;
        }
        if (string.IsNullOrWhiteSpace(password))
        {
            BlazorSession.Current.Toast("require password");
            return;
        }

        client_guid = Guid.NewGuid().ToString();
        string guid = client_guid;

        var bses = BlazorSession.Current;

        viewmode = "connecting";
        client = new Renci.SshNet.SshClient(server, username, password);
        bool threadok = System.Threading.ThreadPool.QueueUserWorkItem(delegate
        {
            try
            {
                client.Connect();
            }
            catch (Exception x)
            {
                if (guid != client_guid)
                    return;

                viewmode = "login";

                bses.InvokeInRenderThread(delegate
                {
                    bses.Alert("Error", x.ToString());
                    StateHasChanged();
                });
                return;
            }

            try
            {
                if (guid != client_guid)
                    return;

                shellstream = client.CreateShellStream("BlazorPlusShell", 80, 24, 800, 600, 4096);
            }
            catch (Exception x)
            {
                if (guid != client_guid)
                    return;


                bses.InvokeInRenderThread(delegate
                {
                    bses.Alert("Error", x.ToString());
                });
                client.Dispose();
                return;
            }

            if (guid != client_guid)
                return;


            _notifylineready = null;
            linequeue.Clear();

            viewmode = "connected";
            bses.InvokeInRenderThread(delegate
            {
                bses.Toast("Connected");

                BlazorSession.Current.Browser.SetMemoryItem("console_server", server);
                BlazorSession.Current.Browser.SetMemoryItem("console_username", username);
                BlazorSession.Current.Browser.SetMemoryItem("console_password", password);

                StateHasChanged();
            });

            try
            {
                WorkLoop(bses, guid);

                bses.InvokeInRenderThread(delegate
                {
                    bses.ConsoleWarn("WorkLoop END");
                });
            }
            catch (Exception x)
            {
                viewmode = "login";
                bses.InvokeInRenderThread(delegate
                {
                    BlazorSession.Current.Alert("Error", x.ToString());
                    StateHasChanged();
                });
            }
            try
            {
                if (client != null)
                {
                    client.Dispose();
                }
            }
            catch (Exception)
            {

            }
        });
        if (!threadok)
        {
            viewmode = "login";
            BlazorSession.Current.Alert("Error", "Try again later.");
            return;
        }

    }

    void WorkLoop(BlazorSession bses, string guid)
    {
        client.ErrorOccurred += (sender, args) =>
        {
            viewmode = "login";
            bses.InvokeInRenderThread(delegate
            {
                BlazorSession.Current.Alert("Error", args.Exception.ToString());
                StateHasChanged();
            });
            client.Dispose();
        };



        using System.IO.StreamReader sr = new System.IO.StreamReader(shellstream);

        while (!_isdisposed && client.IsConnected)
        {
            System.Threading.Thread.Sleep(10);
            //TODO:???

            if (guid != client_guid)
                return;

            string line = sr.ReadLine();
            if (line == null)
                continue;

            if (guid != client_guid)
                return;


            lock (linequeue)
                linequeue.Enqueue(line);
            if (_notifylineready != null)
                _notifylineready();
        }
    }

    Queue<string> linequeue = new Queue<string>();
    Action _notifylineready;

}

@if (viewmode == null)
{
    if (BlazorSession.Current.Browser == null)
    {
        BlazorSession.Current.SetTimeout(10, delegate
        {
            StateHasChanged();
        });
        return;
    }

    server = BlazorSession.Current.Browser.GetMemoryItem("console_server") as string ?? "localhost";
    username = BlazorSession.Current.Browser.GetMemoryItem("console_username") as string ?? "";//pi
    password = BlazorSession.Current.Browser.GetMemoryItem("console_password") as string ?? "";//raspberry

    viewmode = "login";
}

@if (viewmode == "login")
{

    <style>
        .label_1 {
            display: inline-block;
            width: 100px;
            text-align: right;
        }
    </style>
    <h1>Login</h1>
    <EditForm Model="this">
        <span class="label_1">server:</span><InputText @bind-Value="server" />
        <br />
        <span class="label_1">username:</span><InputText @bind-Value="username" />
        <br />
        <span class="label_1">password:</span><InputText type="password" @bind-Value="password" />
        <br />
        <span class="label_1"></span><button @onclick="DoLogin">Login</button>
    </EditForm>

    return;
}

@if (viewmode == "connecting")
{
    void DoCancel()
    {
        viewmode = "login";
        client_guid = Guid.NewGuid().ToString();
        client.Dispose();
    }
    <div>
        Connecting... @server as @username , Please wait...
        <br />
        <button @onclick="DoCancel">Cancel</button>
    </div>
    return;
}



<h1>SSH Console</h1>



<p>
    Connected:@client.IsConnected
    <br />
    @client.ConnectionInfo.ServerVersion
</p>

<BlazorDomTree OnRootReady="bdtready" style="max-width:90%;" />

<hr/>

<div>
    tips
</div>
<div>
    Run a app and keep it alive : <span style="color:blue">sudo nohup /home/pi/dotnet/dotnet myprogram.dll &</span>
</div>

@code
{
    void bdtready(BlazorDomTree bdt)
    {
        BlazorSession ses = BlazorSession.Current;

        PlusControl resultdiv = bdt.Root.Create("div style='margin:8px 0;max-width:100%;height:400px;background-color:black;color:white;overflow-y:scroll;padding:5px 5px 15px;'");

        PlusControl div1 = bdt.Root.Create("div style='display:flex;'");
        // div1.Create("label style='width:100px").InnerText("Command:");
        PlusControl inpword = div1.Create("input type='text' style='flex:99;padding:0 5px'");
        PlusControl button = div1.Create("button").InnerText("Send");
        div1.Create("button").InnerText("^C").OnClick(delegate
        {
            shellstream.WriteByte(3);
            shellstream.Flush();
        });

        string _lastcolor = null;
        PlusControl SetLastColor(PlusControl span)
        {
            if (_lastcolor != null)
            {
                span.Style("color", _lastcolor);
                if (_lastcolor == "black")
                {
                    span.Style("background-color", "white");
                }
            }
            return span;
        }

        void SendLine(string line, string color)
        {
            ses.ConsoleLog("line", System.Text.Json.JsonSerializer.Serialize(line));

            var d = resultdiv.Create("div style='color:" + color + "'");
            if (string.IsNullOrEmpty(line))
            {
                d.InnerHTML("<br/>");
                return;
            }

            if (line.IndexOf("\x1b[") == -1)
            {
                if (color == null)
                    SetLastColor(d);
                d.InnerText(line);
                return;
            }

            //parse ssh color:
            int pos = 0;
            while (pos < line.Length)
            {
                int p33 = line.SafeIndexOf("\x1b[", pos);
                if (p33 == -1)
                {
                    string laststr = line.Substring(pos);
                    if (!string.IsNullOrEmpty(laststr))
                    {
                        //ses.ConsoleLog("last", System.Text.Json.JsonSerializer.Serialize(laststr));
                        SetLastColor(d.Create("span")).InnerText(laststr);
                    }
                    break;
                }

                ses.ConsoleLog("p33-a", p33);

                if (line[p33] != '\x1b')    //a bug for raspbian - dotnet-linux-arm
                {
                    ses.ConsoleWarn("invalid IndexOf implementation");
                    if (line[p33 - 1] == '\x1b') p33--;
                }

                if (p33 > pos)
                {
                    //ses.ConsoleLog("normal", System.Text.Json.JsonSerializer.Serialize(line.Substring(pos, p33 - pos)));
                    SetLastColor(d.Create("span")).InnerText(line.Substring(pos, p33 - pos));
                }


                pos = p33 + 2;
                if (pos >= line.Length)
                    break;
                p33 = line.SafeIndexOf("\x1b[K", pos);    //?
                if (p33 == -1)
                {
                    //invalid
                    _lastcolor = "red";
                    SetLastColor(d.Create("span")).InnerText(line.Substring(pos));
                    break;
                }

                ses.ConsoleLog("p33-b", p33);

                if (line[p33] != '\x1b')    //a bug for raspbian - dotnet-linux-arm
                {
                    ses.ConsoleWarn("invalid IndexOf implementation");
                    if (line[p33 - 1] == '\x1b') p33--;
                }


                string controlcode = line.Substring(pos, p33 - pos);
                //d.Create("b").Style("color", "cyan").InnerText(controlcode);
                //ses.ConsoleLog("control", System.Text.Json.JsonSerializer.Serialize(controlcode));

                if (controlcode == "m")
                {
                    _lastcolor = null;
                }
                else if (controlcode.StartsWith("01;"))
                {
                    _lastcolor = GetColor(controlcode.Substring(3));
                }

                pos = p33 + 3;

            }

        }
        void ScrollBottom()
        {
            resultdiv.Eval("this.scrollTop=this.scrollHeight");
        }


        inpword.SetFocus(99);



        _notifylineready = delegate ()
        {
            ses.InvokeInRenderThread(delegate
            {
                List<string> list = new List<string>();
                lock (linequeue)
                {
                    list.AddRange(linequeue);
                    linequeue.Clear();
                }
                foreach (string line in list)
                {
                    SendLine(line, null);
                }
                ScrollBottom();
            });
        };

        void SendCommand()
        {
            if (inpword.Attribute_Disabled)
                return;

            string cmdline = inpword.Value.Trim();

            inpword.Value = "";

            resultdiv.Create("div style='margin:1px;border-top:solid 1px white'");
            //SendLine(cmdline, "yellow");

            ScrollBottom();

            inpword.Attribute_Disabled(true);
            button.Attribute_Disabled(true);

            System.Threading.Thread t = new System.Threading.Thread(delegate ()
            {
                try
                {
                    shellstream.WriteLine(cmdline);
                }
                catch (Exception x)
                {
                    ses.InvokeInRenderThread(delegate
                    {
                        SendLine(x.ToString(), "red");
                    });
                }
                ses.InvokeInRenderThread(delegate
                {
                    inpword.Attribute_Disabled(false);
                    button.Attribute_Disabled(false);
                    //resultdiv.Create("div style='margin:1px;border-top:solid 1px white'");
                    ScrollBottom();
                    inpword.SetFocus();

                    this.StateHasChanged();
                });
            });
            t.Start();


        }

        inpword.OnEnterKey(delegate { SendCommand(); });
        button.OnClick(delegate { SendCommand(); });


        PlusControl div2 = bdt.Root.Create("div style='display:flex;'");

        List<string> cmds = new List<string>();
        cmds.Add("ls");
        cmds.Add("ps -ef|grep dotnet");

        foreach (string cmd in cmds)
        {
            div2.Create("button").InnerText(cmd).OnClick(delegate
            {
                resultdiv.Create("div style='margin:1px;border-top:solid 1px white'");
                shellstream.WriteLine(cmd);
                shellstream.Flush();
            });
        }

    }


    static string GetColor(string code)
    {
        switch (code)
        {
            case "30m": return "black";
            case "31m": return "red";
            case "32m": return "green";
            case "33m": return "yellow";
            case "34m": return "blue";
            case "35m": return "purple";
            case "36m": return "skyblue";
            case "37m": return "white";
        }
        return null;
    }
}